import {accessSync, constants, existsSync, readFileSync, writeFileSync} from "fs";
import {app} from "electron";
import _ from "lodash";
import sudo from "sudo-prompt";
import os from "os"
import moment from "moment";
import fetch from "node-fetch"


const remote = process.type === "browser" ? require("@electron/remote/main") : require("@electron/remote")

const words = "qwertyuioplkjhgfdsazxcvbnm";
const WORDS = words.toUpperCase();
const digits = "0123456789";

export interface BakFileFormat {
    saveTime: number,
    mHostsVersion: string
    hosts: Host[]
}

export function guid(length: number = 32, src: string = words + WORDS + digits): string {
    if (length < 1) throw new Error("长度不能小于1");
    const srcLen = src.length;
    if (srcLen < 3) throw new Error("源字符串长度不能小于3");
    let id = "";
    for (let i = 0; i < length; i++) {
        id += src[rand(0, srcLen - 1)];
    }
    return id;
}

export function rand(min: number, max: number): number {
    return Math.round(min + (max - min) * Math.random());
}

export function isMac() {
    return process.platform === 'darwin';
}

export function isLinux() {
    return process.platform === 'linux';
}

export function isWindows() {
    return process.platform === 'win32';
}

export function getHostsFilePath() {
    return isWindows() ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts";
}

export function fetchSysHosts() {
    return readFileSync(getHostsFilePath(), {encoding: "utf-8"})
}

export interface Settings {
    lastSaveTime: number
    hosts: HostMap
    defaultHosts: string
    lastUpdateTime: number
}

export interface IHost {
    id: string
    name: string
    lastUpdateTime?: number
    createTime?: number
    isActive: boolean
    icon?: string
}

export interface Host extends IHost {
    content: string
}

export type HostMap = { [id: string]: Host }

export function defaultSettings(): Settings {
    const userDataPath = (process.type === 'browser' ? app : remote.app).getPath("userData");
    const id1 = guid();
    const id2 = guid();
    return {
        lastUpdateTime: 0,
        lastSaveTime: Date.now(),
        hosts: {
            [id1]: {
                id: id1,
                name: "开发环境",
                content: "# 开发环境\n\n",
                isActive: false
            },
            [id2]: {
                id: id2,
                name: "生产环境",
                content: "# 生产环境\n\n",
                isActive: false
            }
        },
        defaultHosts: readFileSync(`${userDataPath}/systemDefaultHosts.bak`, {encoding: 'utf-8'})
    };
}

export function loadSettings(): Settings {
    if (!existsSync(settingFilePath())) {
        writeFileSync(`${userDataPath()}/systemDefaultHosts.bak`, fetchSysHosts());
        writeFileSync(settingFilePath(), JSON.stringify(defaultSettings()))
    }
    console.log(`当前用户数据目录为：${userDataPath()}`);
    const settings = JSON.parse(readFileSync(settingFilePath(), {encoding: 'utf-8'}));
    return _.merge({
        hosts: {},
        groups: {
            "other-group": {
                id: "other-group",
                name: "未分组",
                hosts: settings.hosts
            }
        }
    }, settings);
}

export function isReadable() {
    try {
        accessSync(getHostsFilePath(), constants.R_OK);
        return true;
    } catch (e) {
        return false;
    }
}

export function isWritable() {
    return writeHostString2System(fetchSysHosts());
}

export function getApp() {
    return process.type === "browser" ? app : remote.app;
}

export function writeHostString2System(hosts: string): boolean {
    try {
        const app = getApp();
        const info = `# Hosts Apply By ${app.getName()}-v${app.getVersion()} ${moment().format("YYYY-MM-DD HH:mm:ss")}`;
        writeFileSync(
            getHostsFilePath(),
            `${info}\n\n${hosts}`,
            {encoding: "utf-8"}
        );
        return true;
    } catch (e) {
        return false;
    }
}

export async function writeHost2System(host: Host): Promise<string> {
    return new Promise((resolve, reject) => {
        const settings = loadSettings();
        const hosts = Object.values(settings.hosts)
            .filter(item => item.id !== host.id && item.isActive);
        if (!host.isActive) {
            hosts.push(host);
        }
        const hostLines = _.flatten(
            hosts.map(item => item.content.split("\n"))
        ).map(line => line.trim()).filter(line => line.startsWith("#") || isUsefulRule(line));

        if (isWritable()) {
            const res = writeHostString2System(hostLines.join("\n"));
            res ? resolve("保存成功") : reject("写入文件失败");
            return;
        }
        const user = os.userInfo();
        const chownCmd = isWindows()
            ? `cacls ${getHostsFilePath()} /E /G ${user.username}:F`
            : `chown ${user.uid}:${user.gid} ${getHostsFilePath()}`;
        sudo.exec(
            chownCmd,
            {
                name: getApp().getName()
            },
            (error, stdout, stderr) => {
                if (error || !stderr) {
                    reject(error || stderr)
                } else {
                    const res = writeHostString2System(hostLines.join("\n"));
                    res ? resolve("保存成功") : reject("写入文件失败");
                }
            }
        );
    })
}

export function settingFilePath() {
    return `${userDataPath()}/settings.json`;
}

export function userDataPath() {
    return getApp().getPath("userData")
}

export function writeSetting2File(settings: Settings) {
    writeFileSync(settingFilePath(), JSON.stringify(settings))
}

export const writeSetting2FileDebounce = _.debounce(writeSetting2File, 800);

export function refreshDNS(): Promise<string> {
    return new Promise((resolve, reject) => {
        let cmd;
        if (isMac()) {
            cmd = "killall -HUP mDNSResponder";
        } else if (isLinux()) {
            cmd = "/etc/init.d/networking restart"
        } else if (isWindows()) {
            cmd = "ipconfig /flushdns";
        } else {
            alert("未检测到系统类型")
        }
        cmd && sudo.exec(cmd, {
            name: getApp().getName()
        }, (error, stdout, stderr) => {
            if (error || stderr) {
                reject(`刷新DNS失败: ${error || stderr}`)
            } else {
                resolve("刷新DNS成功")
            }
        });
    })
}

export interface NodePackage {
    version: string
    name: string
    description: string
}

export function update(): Promise<NodePackage> {
    return new Promise((resolve, reject) => {
        fetch('https://gitee.com/iamhefang/mhosts/raw/master/package.json')
            .then(res => res.text())
            .then(text => resolve(eval(`(${text})`)))
            .catch(reject)
    })
}

/**
 * 版本号对比，
 * @param version1
 * @param version2
 * @return version1>version2, 返回1  version1===version2, 返回0， version1<version2, 返回-1
 */
export function versionCompare(version1: string, version2: string) {
    const v1 = fillVersionArray(version1.split("."));
    const v2 = fillVersionArray(version2.split("."));
    for (let i = 0; i < v1.length; i++) {
        if (v1[i] > v2[i]) return 1;
        if (v1[i] < v2[i]) return -1;
    }
    return 0;
}

export function fillVersionArray(arr: string[]): number[] {
    return [0, 0, 0].map((item, index) => arr[index] ? +arr[index] : item)
}

export enum MessageType {
    success = "success",
    error = "error"
}

export const msgTitleMap: { [type in MessageType]: string } = {
    success: '成功',
    error: '出现错误'
}


const hostReg = /^([^ ]*?)([ \t\r]+([^ ]*?))+$/;
const usefulHostReg = /^([0-9a-f:.]*?)[ \t\r]+([^.].*[^.])$/i;
const ipReg = /^[0-9a-f:.]+$/i;


export function isUsefulRule(line: string) {
    return usefulHostReg.test(line)
}

export function isValidRule(line: string) {
    return hostReg.test(line)
}

//todo: ip判断算法待改进
export function isValidIp(ip: string) {
    return ipReg.test(ip)
}

export function isValidHost(host: string) {
    const lower = host.toLowerCase();
    return (lower !== "localhost" && !lower.includes(".")) || lower.startsWith(".") || lower.endsWith(".");
}